package com.gitee.qdbp.base.shiro.mgt;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitee.qdbp.able.beans.Duration;
import com.gitee.qdbp.base.shiro.bean.OnlineUser;
import com.gitee.qdbp.base.shiro.cache.VolatileCache;
import com.gitee.qdbp.base.shiro.realm.ManagableLoginRealm;
import com.gitee.qdbp.base.shiro.session.VolatileCachingManager;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * 在线用户管理
 *
 * @author zhaohuihua
 * @version 180830
 */
public class DefaultOnlineManager implements OnlineManager, VolatileCachingManager {

    private static Logger log = LoggerFactory.getLogger(DefaultOnlineManager.class);

    protected DefaultWebSecurityManager securityManager;

    protected ExecutorService executors = Executors.newCachedThreadPool();

    public DefaultWebSecurityManager getSecurityManager() {
        return securityManager;
    }

    public void setSecurityManager(DefaultWebSecurityManager securityManager) {
        this.securityManager = securityManager;
    }

    private Cache<Serializable, Session> getActiveSessionsCache() {
        SessionManager sessionManager = securityManager.getSessionManager();
        if (sessionManager instanceof DefaultSessionManager) {
            SessionDAO sessionDao = ((DefaultSessionManager) sessionManager).getSessionDAO();
            if (sessionDao instanceof CachingSessionDAO) {
                return ((CachingSessionDAO) sessionDao).getActiveSessionsCache();
            }
        }
        return null;
    }

    /** 设置认证信息和授权信息的过期时间 **/
    public void expire(Session session, Duration duration) {
        String principalsKey = DefaultSubjectContext.PRINCIPALS_SESSION_KEY;

        // 认证信息的缓存容器authenticationCache是否存在依赖于配置
        // 默认情况下, 认证信息放在session中, 而不是缓存容器中
        PrincipalCollection principals = (PrincipalCollection) session.getAttribute(principalsKey);
        if (principals == null) {
            // 如果存在认证信息的缓存容器, 调用对应实现类的方法来获取认证信息
        }
        if (principals == null) {
            return;
        }

        Collection<Realm> realms = securityManager.getRealms();
        if (realms != null && !realms.isEmpty()) {
            for (Realm item : realms) {
                if (!(item instanceof ManagableLoginRealm)) {
                    continue;
                }
                ManagableLoginRealm realm = (ManagableLoginRealm) item;
                { // 设置认证信息的过期时间
                    Cache<Object, AuthenticationInfo> cache = realm.getAuthenticationCache();
                    if (cache instanceof VolatileCache) {
                        Object cacheKey = realm.getAuthenticationCacheKey(principals);
                        @SuppressWarnings("unchecked")
                        VolatileCache<Object> vcache = (VolatileCache<Object>) cache;
                        vcache.expire(cacheKey, duration);
                    }
                }
                { // 设置授权信息的过期时间
                    Cache<Object, AuthorizationInfo> cache = realm.getAuthorizationCache();
                    if (cache instanceof VolatileCache) {
                        Object cacheKey = realm.getAuthorizationCacheKey(principals);
                        @SuppressWarnings("unchecked")
                        VolatileCache<Object> vcache = (VolatileCache<Object>) cache;
                        vcache.expire(cacheKey, duration);
                    }
                }
            }
        }

        log.trace("[SetExpireTime]Success: principal={}", principals.getPrimaryPrincipal());
    }

    /** {@inheritDoc} **/
    @Override
    public List<OnlineUser> getAllActiveUsers() {

        String principalsKey = DefaultSubjectContext.PRINCIPALS_SESSION_KEY;
        Cache<Serializable, Session> sessionCache = getActiveSessionsCache();
        if (sessionCache == null) {
            throw new UnsupportedOperationException();
        }

        long now = System.currentTimeMillis();
        List<OnlineUser> list = new ArrayList<>();
        Set<Serializable> keys = sessionCache.keys();
        for (Serializable key : keys) {
            Session session = sessionCache.get(key);
            if (session == null) {
                continue;
            }
            Date lastAccessTime = session.getLastAccessTime();
            if (lastAccessTime != null && lastAccessTime.getTime() + session.getTimeout() > now) {
                continue;
            }
            PrincipalCollection principals = (PrincipalCollection) session.getAttribute(principalsKey);
            if (principals == null) {
                continue;
            }
            OnlineUser bean = new OnlineUser();
            bean.setSessionId(session.getId());
            bean.setStartTimestamp(session.getStartTimestamp());
            bean.setLastAccessTime(session.getLastAccessTime());
            bean.setTimeout(session.getTimeout());
            bean.setHost(session.getHost());
            bean.setAccount(principals.getPrimaryPrincipal());
        }
        return list;
    }

    /** {@inheritDoc} **/
    @Override
    public void clearMyAuthorizationInfo() {
        Session session = SecurityUtils.getSubject().getSession(false);
        if (session == null) return;
        String principalsKey = DefaultSubjectContext.PRINCIPALS_SESSION_KEY;
        PrincipalCollection principals = (PrincipalCollection) session.getAttribute(principalsKey);

        // 认证信息的缓存容器authenticationCache是否存在依赖于配置
        // 默认情况下, 认证信息放在session中, 而不是缓存容器中
        if (principals == null) {
            // 如果存在认证信息的缓存容器, 调用对应实现类的方法来获取认证信息
        }
        if (principals == null) {
            return;
        }

        Collection<Realm> realms = securityManager.getRealms();
        if (realms != null && !realms.isEmpty()) {
            for (Realm item : realms) {
                if (item instanceof ManagableLoginRealm) {
                    ManagableLoginRealm realm = (ManagableLoginRealm) item;
                    Cache<Object, AuthorizationInfo> cache = realm.getAuthorizationCache();
                    Object cacheKey = realm.getAuthorizationCacheKey(principals);
                    cache.remove(cacheKey);
                }
            }
        }
    }

    /** {@inheritDoc} **/
    @Override
    public void clearAllAuthorizationInfo() {
        executors.execute(new SessionEachRunner(false));
    }

    /** {@inheritDoc} **/
    @Override
    public void clearAuthorizationInfo(String userId) {
        if (VerifyTools.isBlank(userId)) {
            return;
        }
        executors.execute(new SessionEachRunner(Arrays.asList(userId), false));
    }

    /** {@inheritDoc} **/
    @Override
    public void clearAuthorizationInfo(List<String> userIds) {
        if (VerifyTools.isBlank(userIds)) {
            return;
        }
        executors.execute(new SessionEachRunner(userIds, false));
    }

    /** {@inheritDoc} **/
    @Override
    public void clearAuthenticationInfo() {
        executors.execute(new SessionEachRunner(true));
    }

    /** {@inheritDoc} **/
    @Override
    public void clearAuthenticationInfo(String userId) {
        if (VerifyTools.isBlank(userId)) {
            return;
        }
        executors.execute(new SessionEachRunner(Arrays.asList(userId), true));
    }

    /** {@inheritDoc} **/
    @Override
    public void clearAuthenticationInfo(List<String> userIds) {
        if (VerifyTools.isBlank(userIds)) {
            return;
        }
        executors.execute(new SessionEachRunner(userIds, true));
    }

    private class SessionEachRunner implements Runnable {

        /** 是否结束会话 **/
        private boolean stop;
        /** 清除指定用户, 如果为空则清除所有用户 **/
        private List<String> cacheKeys;

        /** 清除所有 **/
        public SessionEachRunner(boolean stop) {
            this.stop = stop;
        }

        /** 清除指定用户 **/
        public SessionEachRunner(List<String> cacheKeys, boolean stop) {
            VerifyTools.requireNotBlank(cacheKeys, "cacheKeys");
            this.cacheKeys = cacheKeys;
            this.stop = stop;
        }

        @Override
        public void run() {
            String op = stop ? "StopSession" : "ClearAuthorization";
            try {
                doExecute(op);
            } catch (Throwable e) {
                log.error("[{}]Error", op, e);
            }
        }

        protected void doExecute(String op) {
            String principalsKey = DefaultSubjectContext.PRINCIPALS_SESSION_KEY;

            // 认证信息的缓存容器authenticationCache是否存在依赖于配置
            // 默认情况下, 认证信息放在session中, 而不是缓存容器中
            Cache<Serializable, Session> sessionCache = getActiveSessionsCache();

            Collection<Realm> realms = securityManager.getRealms();
            if (realms == null || realms.isEmpty()) {
                return;
            }

            Set<Serializable> keys = sessionCache.keys();
            for (Serializable key : keys) {
                Session session = sessionCache.get(key);
                if (session == null) {
                    continue;
                }
                PrincipalCollection principals = (PrincipalCollection) session.getAttribute(principalsKey);
                if (principals == null) {
                    // 如果存在认证信息的缓存容器, 应该用对应实现类的方法来获取认证信息
                }
                if (principals == null) {
                    continue;
                }

                for (Realm item : realms) {
                    if (!(item instanceof ManagableLoginRealm)) {
                        continue;
                    }
                    ManagableLoginRealm realm = (ManagableLoginRealm) item;
                    if (stop) { // 清除认证信息
                        Cache<Object, AuthenticationInfo> cache = realm.getAuthenticationCache();
                        // cacheKey实际上等于IAccount.getId();
                        Object cacheKey = realm.getAuthenticationCacheKey(principals);
                        if (cacheKeys == null || cacheKeys.indexOf(cacheKey) >= 0) {
                            cache.remove(cacheKey);
                            // 清除会话信息
                            if (sessionCache != null) {
                                sessionCache.remove(key);
                            }
                        }
                    }
                    { // 清除授权信息
                        Cache<Object, AuthorizationInfo> cache = realm.getAuthorizationCache();
                        // cacheKey实际上等于IAccount.getId();
                        Object cacheKey = realm.getAuthorizationCacheKey(principals);
                        if (cacheKeys == null || cacheKeys.indexOf(cacheKey) >= 0) {
                            cache.remove(cacheKey);
                        }
                    }
                }
                log.trace("[{}]Success: sessionKey={}, user={}", op, key, principals.getPrimaryPrincipal());
            }
        }

    }
}
